/*
 * User: rant
 * Date: 23/01/2007
 *
 *
 */
package laMalinche.rete;


import laMalinche.actObservable.BasicActObservableCondition;
import laMalinche.actObservable.BasicObservableAction;
import laMalinche.actObservable.FieldRuleObservableAction;
import laMalinche.LaMaContext;
import laMalinche.Pair;
import laMalinche.DevMode;
import laMalinche.constraint.ConstraintManager;
import laMalinche.editableContext.AboutToChangeManager;
import laMalinche.rete.conditions.BasicEditingCondition;
import laMalinche.rete.conditions.EmptyFieldCondition;
import laMalinche.rete.nodes.*;
import laMalinche.rete.nodes.editing.BasicEditingConditionNode;
import laMalinche.rete.nodes.tailing.BasicTailingsConditionNode;
import laMalinche.rete.rule.*;
import static laMalinche.rete.rule.BusinessRuleType.constraint;
import static laMalinche.rete.rule.BusinessRuleType.suggestedValue;
import org.jetbrains.annotations.NotNull;

import java.util.*;

import com.google.inject.Inject;

/**
 This singleton trnslate Buisness rules to production rules
 */
public class ConditionNodeFactory {
    @Inject
    private ConstraintManager constraintManager;
    // private Log log =  LogService.getLog(ConditionNodeFactory.class);
    @Inject
    LaMaContext laMaContext;
    // todo, move to one per laMalinche
	private final Map<Pair<?, BusinessRuleType>, ConditionNode> condiMap = new HashMap<Pair<?, BusinessRuleType>, ConditionNode>();

    @Inject
    private Rete rete;

    public ConstraintManager getConstraintManager() {
		return constraintManager;
	}

    public void setConstraintManager(final ConstraintManager constraintManager) {
		this.constraintManager = constraintManager;
	}


    private ConditionNodeFactory() {
         DevMode.getInstance().getInjector().injectMembers(this);
    }

//	// what is the status of the ValidationResult?? in LaMalinche Framework??
//	private class NonMan extends TrigerConditionNode implements AfterTrigerListener {
//		public void wasTrigered(final TrigerConditionNode trigerConditionNode) {
//			if (true) update();
//		}
//	}

	public Map<Pair<?, BusinessRuleType>, ConditionNode> getCondiMap() {
		return condiMap;
	}

	public Rete getRete() {
		return rete;
	}

	public ConditionNode createConditionNode(Condition condition, BusinessRuleType businessRuleType) {
		return createConditionNode(condition, businessRuleType, false);
	}

	public ConditionNode createConditionNode(final Action action, final BusinessRuleType businessRuleType) {
		return createConditionNode(action, businessRuleType, true);
	}

	private ConditionNode createConditionNode(Object condition, BusinessRuleType businessRuleType, boolean actionTailing) {
		ConditionNode conditionNode;
//		final Pair<?, BusinessRuleType> pair = new Pair<Object, BusinessRuleType>(new Adapter(condition), businessRuleType);

		final Pair<?, BusinessRuleType> pair = new Pair<Object, BusinessRuleType>(condition, businessRuleType);
		if (condiMap.containsKey(pair)) {
			conditionNode = condiMap.get(pair);
		} else {
			Class<? extends ConditionNode> conditionNodetype = getNewConditionNode(businessRuleType, actionTailing);
			conditionNode = getNewConditionNode(businessRuleType, condition, conditionNodetype);
			condiMap.put(pair, conditionNode);
		}
		return conditionNode;
	}

	private Class<? extends BasicTailingsConditionNode> getNewConditionNode(BusinessRuleType type, boolean actionTailing) {
		return (actionTailing) ? ActionTailingConditionNode.class : BasicTailingsConditionNode.class;
	}

	protected ConditionNode getNewConditionNode(BusinessRuleType type, Object condition, Class<? extends ConditionNode> nodeType) {
		ConditionNode conditionNode;
		if (condition instanceof ConditionNode) {
			conditionNode = (ConditionNode) condition;
		} else {
			conditionNode = noExceptionsNewInstance(nodeType);
			if (condition instanceof Action) {
				final ActionHolder actionHolder = ((ActionHolder) conditionNode);
				actionHolder.setAction((Action) condition);

				if (!type.isTriger()) {
					actionHolder.setTrigger(false);
				} else {
					actionHolder.setTrigger(true);
				}
			} else {
				conditionNode.setCondition((Condition) condition);
			}
		}
		conditionNode.initialize();
		return conditionNode;
	}

	private AboutToChangeManager getAboutToChangeManager() {
		return rete.getAboutToChangeManager();
	}



	protected <T> T noExceptionsNewInstance(@NotNull Class<T> clazz) {
		T result;
		try {
			result = clazz.newInstance();
		} catch (InstantiationException e) {
			throw new RuntimeException(e);
		} catch (IllegalAccessException e) {
			throw new RuntimeException(e);
		}
		return result;
	}



	public Stack<Relevant> getConditions(final ProductionRule rule) {
//		final Action action = rule.getAction();
		final Action action = getObservableAction(rule);
		rule.setAction(action);
		//todo

		Stack<Relevant> reteNodesStack = new Stack<Relevant>();
		final BusinessRuleType businessRuleType = rule.getProductionRuleType();
		for (Condition condition : rule.getConditions()) {
			Condition  actObservable = conditionToObservable(condition);
			ConditionNode conditionNode = createConditionNode(actObservable, businessRuleType);
			reteNodesStack.add(conditionNode);
			if (businessRuleType.isEditableContextSensitive()) {
				final EditingConditionNode editingConditionNode = cerateEditingNode(conditionNode, businessRuleType);
				reteNodesStack.add(editingConditionNode);
			}
		}
		// do not do it to property
		if (!businessRuleType.isDefault()) {
			final ConditionNode actionTailingConditionNode = createConditionNode(rule.getAction(), businessRuleType);
			reteNodesStack.add(actionTailingConditionNode);
			if (businessRuleType.isEditableContextSensitive()) {
				final EditingConditionNode basicEditingConditionNode = cerateEditingNode(actionTailingConditionNode, businessRuleType);
				reteNodesStack.add(basicEditingConditionNode);
			}
		}
//		final BusinessRuleType businessRuleType1 = rule.getProductionRuleType();
		if (suggestedValue.equals(businessRuleType)) {
			for (Relevant relevant : reteNodesStack) {
				final ConditionNode conditionNode = (ConditionNode) relevant;
				if (((BasicActObservableCondition) conditionNode.getCondition()).getCondition() instanceof EmptyFieldCondition) {
					final EditingConditionNode conditionNode1 = cerateEditingNode(conditionNode, businessRuleType);
					reteNodesStack.add(conditionNode1);
					break;
				}
			}
		}
		if (rule.getProductionRuleType().equals(constraint)) {
			if (rule.getPriority() == 0) {
				reteNodesStack.add(laMaContext.getConstraintManager().getMandatoryFieldValidationCondition());
			} else {
				reteNodesStack.add(laMaContext.getConstraintManager().getNonMandatoryFieldValidationCondition());
			}
		}
        if (rule.getTrigeredBy()!=null){
            String trigerName = rule.getTrigeredBy();
            reteNodesStack.add(LaMaContext.getInstance().getTrigerCondition(trigerName));
        }
        return reteNodesStack;
	}


	// todo
	public Action getObservableAction(final ProductionRule rule) {
		Action result;
		final BusinessRuleType ruleType = rule.getProductionRuleType();
		final Action action = rule.getAction();
		if (ruleType.isDerivationField()){
			result = new FieldRuleObservableAction((FieldAction) action);
		} else {
		result = new BasicObservableAction(action);
		}

		return result;
	}

	// here conditions are adapted to observable use BasicActObservable
	private Condition conditionToObservable(final Condition condition) {
        //		if (condition instanceof FieldCondition){
//			observableCondition = new FieldRuleObservableAction(condition)
//		}
        return new BasicActObservableCondition(condition);
	}

	private EditingConditionNode cerateEditingNode(final ConditionNode conditionNode, final BusinessRuleType businessRuleType) {
		BasicEditingConditionNode editingConditionNode;
		final Pair<?, BusinessRuleType> pair = new Pair<Object, BusinessRuleType>(conditionNode, businessRuleType);
		if (condiMap.containsKey(pair)) {
			editingConditionNode = (BasicEditingConditionNode) condiMap.get(pair);
		} else {
			editingConditionNode = new BasicEditingConditionNode();
			editingConditionNode.initialize();
			editingConditionNode.setBusinessRuleType(businessRuleType);
			editingConditionNode.setAboutToChangeManager(getAboutToChangeManager());
			final BasicEditingCondition xCondition = new BasicEditingCondition();
			xCondition.setCondition(conditionNode);
			editingConditionNode.setCondition(xCondition);
			condiMap.put(pair, editingConditionNode);
		}
		return editingConditionNode;
	}



}